package va;

import java.util.Arrays;

/*
 * Range class added by Eric Beerman
 * Defines ranges used by the VA file in order to make the approximations.
 */

public class Range {

	public int lowerBound;
	public int upperBound;
	
	public Range (int lowerBound, int upperBound) {
		this.lowerBound = lowerBound;
		this.upperBound = upperBound;
	}
	
	/*
	 * Function that takes in the number of bits total for that dimension of the vector,
	 * along with the total range of possible int values for the dimension, and returns the
	 * ranges to be used in making the approximation.
	 * Example: if dimension has 2 bits and the valueRange goes from 0-100, 4 ranges would return
	 * from 0-25, 26-50, 51-75, 76-100 
	 */
	public static Range[] DefineRanges(int numBits, int lowerRange, int upperRange) {
		int valueRange = upperRange - lowerRange + 1; // Plus 1 to make it 0-based
		int numRanges = (int) Math.pow(2, numBits);
		Range[] result = new Range[numRanges];
		int currentLower = lowerRange;
		int baseRange = (int) Math.floor(valueRange/numRanges); // Minus 1 to make it 0-based
		int remainder = valueRange - (baseRange * numRanges);
		int currentUpper = currentLower + baseRange - 1;
		for (int i = 0; i < numRanges; i++) {
			if (i < remainder) currentUpper += 1;
			result[i] = new Range(currentLower, currentUpper);
			currentLower = currentUpper + 1;
			currentUpper += baseRange;
		}
		return result;
	}
	
	/*
	 * Function that takes an integer and Range array, and returns the index of the range to which the integer belongs. 
	 * It performs a binary search for effective retrieval
	 */
	public static int FindRangeIndex(Range[] ranges, int value) {
		int numRanges = ranges.length;
		if (numRanges == 1 && (value < ranges[0].lowerBound || value > ranges[0].upperBound)) {
			// Value does not exist in defined ranges
			return -1;
		}
		int midIndex = (int) Math.floor(numRanges/2);
		Range midRange = ranges[midIndex];
		if (value >= midRange.lowerBound && value <= midRange.upperBound) {
			return midIndex;
		} else if (value > midRange.upperBound) {
			Range[] upperHalf = Arrays.copyOfRange(ranges, midIndex + 1, numRanges);
			int upperValue = FindRangeIndex(upperHalf, value);
			if (upperValue == -1) return upperValue;
			else return midIndex + 1 + upperValue; 
		} else {
			Range[] lowerHalf = Arrays.copyOfRange(ranges, 0, midIndex);
			return FindRangeIndex(lowerHalf, value);
		}
	}
	
	/*
	 * Function that takes an array of ranges and an approximation value (converted to an integer) and finds
	 * the integer value that best approximates it, within the ranges. If we don't do this, then the approx value
	 * is no help.
	 * 
	 */
	public static int FindApproxInt(Range[] ranges, int approx)
	{
		int min = ranges[0].lowerBound, max = ranges[ranges.length - 1].upperBound;
		int numRanges = ranges.length;
	
		int rangeStepSize = (int)Math.ceil((float)(max - min) / (float)numRanges);
		float approxValue = rangeStepSize * approx; //approx should go from 0 to 2^n, n being number of approx bits
		int result = (int)Math.ceil(approxValue); //always ceiling, to make sure it stays in range
		return result;
	
	}
	
	//Gets maximum possible distance used in VAFileRangeScan and VAFileNNScan functions.
	// This function will return the bound that is furthest possible from the target in the range.
	
	public static short GetMaxPossibleDistance(Range range, int target) {
		int upperDifference = Math.abs(range.upperBound - target);
		int lowerDifference = Math.abs(range.lowerBound - target);
		if (lowerDifference > upperDifference) 
			return (short)range.lowerBound;
		 else
			return (short)range.upperBound;
		
	}
	
	//Gets minimum possible distance used in VAFileRangeScan and VAFileNNScan functions.
	// This function will return the bound that is closest possible from the target in the range.
	
	public static short GetMinPossibleDistance(Range range, int target) {
		if (target <= range.upperBound && target >= range.lowerBound) return (short)target;
		int upperDifference = Math.abs(range.upperBound - target);
		int lowerDifference = Math.abs(range.lowerBound - target);
		if (lowerDifference < upperDifference) 
			return (short)range.lowerBound;
		 else
			return (short)range.upperBound;
	}
	
}
